home *** CD-ROM | disk | FTP | other *** search
/ Mac-Source 1994 July / Mac-Source_July_1994.iso / C and C++ / System / ThreadLib 1.0d1 / Source / ThreadLib / ThreadLib.c next >
Encoding:
Text File  |  1994-02-11  |  33.3 KB  |  859 lines  |  [TEXT/KAHL]

  1. /* See the file Distribution for distribution terms.
  2.     (c) Copyright 1994 Ari Halberstadt */
  3.  
  4. /*
  5.     Description
  6.     -----------
  7.  
  8.     ThreadLib implements nonpreemptive multiple thread execution within
  9.     a single application. It does not require any extensions, should work
  10.     with all Macintosh models (from the Plus on up), and works with
  11.     systems 6.0 (tested on 6.0.5) under Finder or MultiFinder, and
  12.     system 7.0. ThreadLib compiles into a small library of about 4.5K,
  13.     so it won't add much overhead to your application. A small test
  14.     application demonstrates how threads are used threads. Best of all,
  15.     the source code is free.
  16.  
  17.     Every thread has its own stack and exception handling environment;
  18.     all other global application data is shared by the threads. Context
  19.     switches are very efficient since they involve only a few operations
  20.     to save the current thread's state, followed by a longjmp to the new
  21.     thread and restoration of the new thread's exception environment. There
  22.     are also no restrictions on the objects that can be allocated on a
  23.     thread's stack.
  24.     
  25.     The rest of this file is heavily commented, so you should be able to
  26.     follow how threads are implemented. Each external function has a
  27.     description of its parameters and what it does.
  28.     
  29.     Notes and Warnings
  30.     ------------------
  31.     
  32.     The thread library must be kept in memory so that longjmp will work.
  33.     The segment containing the thread library must not be unloaded while
  34.     there are any threads.
  35.     
  36.     WARNING: For all threads other than the main thread, some Maintosh Toolbox
  37.     routines may not work correctly if the stack is not between the region of
  38.     memory defined by the low-memory globals CurStackBase and ApplLimit.
  39.     Possibly prohibited are some QuickDraw calls, but I don't actually know
  40.     which Toolbox routines will fail. Some simple tests I ran created a dialog
  41.     with a progress bar; created, opened, read and wrote files; created a
  42.     resource file and added resources to it; allocated memory; and did various
  43.     other operations, all successfully and without problems. If you do encounter
  44.     some problems, you may have to define THREAD_SAVE_GLOBALS in "ThreadLib.h".
  45.     This will enable code to save and restore the same low-memory globals that
  46.     Apple's Thread Manager v1.2 saves and restores during context switches.
  47.     I don't know if defining THREAD_SAVE_GLOBALS will actually do any good, but
  48.     it's worth a try. Since the main thread uses the application's stack, there
  49.     are no restrictions on the Toolbox routines that the main thread may call.
  50.     I am interested in whether you encounter (or don't encounter) limitations
  51.     to Toolbox calls, and would like to know under what conditions the
  52.     limitations arise.
  53.     
  54.     WARNING: Using the THINK C debugger to trace through context switches
  55.     may result in corruption of the application's heap followed by a nasty
  56.     crash. I've used TMON Professional to successfully trace through the
  57.     context switches, and other low-level debuggers (like MacsBug) should
  58.     also work.
  59.     
  60.     NOTE: The trap StackSpace will always return the space remaining in the
  61.     application's stack. This is not the same as the space remaining in
  62.     the current thread's stack. Likewise, using ApplLimit to determine the
  63.     end of a thread's stack will produce incorrect results. StackSpace will,
  64.     however, continue to return the correct value when the main thread
  65.     is executing. To determine the amount of free stack space in a thread,
  66.     use ThreadStackSpace.
  67.     
  68.     Error Handling
  69.     --------------
  70.     
  71.     Errors are handled using my exception library. There are ample comments
  72.     in the file ExceptionLib.c to help you figure out how to use exceptions.
  73.     You may need to modify how errors are handled in order to incorporate
  74.     ThreadLib into your application. The exception handling library uses the
  75.     same macro names as the exception handling macros provided with the THINK
  76.     Class Library (except for the CLEANUP macro), so adapting the thread
  77.     library to work with the TCL should not be too difficult. For other
  78.     applications, you may need to write wrapper functions which call the
  79.     thread library. These functions would use the exception library to catch
  80.     the error and then they would return an error code to the caller.
  81.     Following is an example of a wrapper for ThreadBegin (the letter E stands
  82.     for "Error"):
  83.     
  84.         OSErr EThreadBegin(ThreadProcType entry, ThreadProcType suspend,
  85.             ThreadProcType resume, void *data, size_t stack_size,
  86.             ThreadHandle *result)
  87.         {
  88.             OSErr err = noErr;
  89.             
  90.             TRY {
  91.                 *result = ThreadBegin(entry, suspend, resume, data, stack_size);
  92.             } CATCH {
  93.                 *result = NULL;
  94.                 err = FailReason();
  95.                 NOPROPAGATE;
  96.             } ENDTRY;
  97.             return(err);
  98.         }
  99.  
  100.     other wrapper functions would have a similar form.
  101.     
  102.     To Do
  103.     -----
  104.     
  105.     - EventAvail is a very slow trap to call from within a thread.
  106.     I would like to use a faster method to determine if an event is
  107.     available, but just testing the EventQueue low-memory global
  108.     is not sufficient since update and activate events aren't posted
  109.     to the event queue. Activate events can be detected by checking
  110.     the low-memory globals CurActivate and CurDeactive, but how
  111.     can update events be detected without calling EventAvail?
  112.     If I ever figure out a solution to this problem, then ThreadSchedule
  113.     will no longer give time to other applications, so you'll have to
  114.     allow other applications to run by calling WNE, GNE, or EventAvail
  115.     from within a thread. (Or pass 0-10 ticks for the sleep parameter
  116.     when calling ThreadYield in the main thread, though that would
  117.     probably be less efficient than periodically calling EventAvail.)
  118.     
  119.     - It shouldn't be too difficult to add preemptive multitasking
  120.     to this library. Someday maybe I'll get around to it.
  121.     
  122.     Credits
  123.     -------
  124.     
  125.     Some ideas on how to use setjmp/longjmp to swap stacks were adapted
  126.     from the source for Task Manager v2.2.1 by Michael Hecht
  127.     (Michael_Hecht@mac.sas.com), available at the info-mac archives
  128.     and various other sites.
  129.     
  130.     Thanks to Anton Rang (rang@icicle.winternet.mpls.mn.us) who responded
  131.     to my query on Comp.sys.mac.programmer on how to disable the stack
  132.     sniffer VBL task. (Several other people also responded, but Anton Rang's
  133.     reply was the first to arrive.)
  134.     
  135.     
  136.     Epilogue
  137.     --------
  138.     
  139.     If it took me, a single programmer, 6 days to get threads up and
  140.     running (and that not even full-time), why did it take Apple many
  141.     years and a big fancy extension to get around to implementing
  142.     threads? This hack isn't very difficult. I had something sort-of
  143.     working the first day, but it took me this long to get it reasonably
  144.     stable and to put in all these verbose comments. It would have been
  145.     even easier to implement had I had access to proprietary Apple
  146.     information. If you look at the Thread Manager extension, it's really
  147.     very simple, and it doesn't do much more than what this library does.
  148.     (I developed this library prior to examining what the Thread Manager
  149.     does and did not try to copy the Thread Manager.)
  150.     
  151.     94/02/10 aih - got threads to work without crashing! yay! :-)
  152.     94/02/04 aih - created */
  153.  
  154. #include <limits.h>
  155.  
  156. #if WINTER_SHELL
  157.     #include <SysEqu.h>
  158.     #include "LowMemLib.h"
  159.     #include "MemoryLib.h"
  160.     #include "ThreadLib.h"
  161. #else /* WINTER_SHELL */
  162.     #include "ThreadUtil.h"
  163.     #include "ExceptionLib.h"
  164.     #include "ThreadLib.h"
  165. #endif /* WINTER_SHELL */
  166.  
  167. /*
  168.     Following is a list of functions, constants, and types defined in
  169.     Winter Shell. So that this library can be used without Winter Shell,
  170.     simplified replacements are provided in ThreadUtil.c.
  171.     
  172.     memclr                fills memory with zeros
  173.     HandleValidSize    validates a handle
  174.     HandleBeginClear    allocates a new handle (like NewHandleClear)
  175.     HandleEnd            disposes of a handle (like DisposeHandle)
  176.     PtrValid                validates a pointer
  177.     PtrBegin                allocates a new pointer (like NewPtr)
  178.     PtrEnd                disposes of a pointer (like DisposePtr)
  179.     GetCurStackBase    returns value of CurStackBase low-memory global
  180.     SetStkLowPt            sets value of StkLowPt low-memory global
  181.     LLHAppend            appends an item to the end of a linked list of handles
  182.     LLHDelete            deletes an item from a linked list of handles
  183.     LLHNext                returns the next item in a linked list of handles
  184.     TicksType            typedef'd to unsigned long
  185.     require/check/ensure    assertion macros, similar to the standard assert macro
  186. */
  187.  
  188. #if ! defined(THINK_C) || THINK_C != 5
  189.     /* The register ordering within the jmp_buf type is compiler dependent.
  190.         It should be straightforward to modify this for MPW, and less so
  191.         for a native PowerPC version (though perhaps still possible). For
  192.         MPW and later versions of THINK C you should check the setjmp header
  193.         for the definition of jmp_buf and also check the instructions used to
  194.         save and restore the registers (in THINK C they're inline definitions).
  195.         We only really care about accessing and modifying registers a6 and a7
  196.         in the jump buffer. Also, as currently implemented, setjmp and longjmp
  197.         must not move memory (since they're passed a pointer dereferenced
  198.         from a handle).
  199.         
  200.         If you fix this for THINK C 6.0 or MPW, please let me know what
  201.         changes were necessary so I can incorporate them into the next
  202.         release. */
  203.     #error "only tested with THINK C 5.0.4"
  204. #endif
  205.  
  206. #if defined(THINK_C) && __option(profile)
  207.     /* The THINK C profiler assumes a single execution stack and won't
  208.         work correctly if threads are used. Other profilers may or may
  209.         not work correctly; use them at your own risk. */
  210.     #error "won't work with THINK C profiler"
  211. #endif
  212.  
  213. /* register ordering within jmp_buf (only for THINK C 5.0.4) */
  214. enum {
  215.     d3, d4, d5, d6, d7,
  216.     a1, a2, a3, a4, a6, a7
  217.     /* may be followed by extra floating point registers */
  218. };
  219.  
  220. /* values returned by calls to setjmp */
  221. typedef enum { THREAD_SAVE, THREAD_RUN };
  222.  
  223. /* structure describing state of thread library */
  224. typedef struct {
  225.     short count;                /* number of threads in queue */
  226.     ThreadHandle queue;        /* queue of threads */
  227.     ThreadHandle active;        /* currently active thread */
  228.     ThreadHandle main;        /* main thread */
  229.     ThreadHandle dispose;    /* thread to dispose of */
  230. } ThreadStateType;
  231.  
  232. /* state of thread library */
  233. ThreadStateType gThread;
  234.  
  235. /*----------------------------------------------------------------------------*/
  236. /* validation */
  237. /*----------------------------------------------------------------------------*/
  238.  
  239. /* true if a valid thread */
  240. Boolean ThreadValid(ThreadHandle thread)
  241. {
  242.     if (! HandleValidSize(thread, sizeof(ThreadType))) return(false);
  243.     if (thread == gThread.main) {
  244.         if ((**thread).stack) return(false);
  245.         if ((**thread).entry) return(false);
  246.     }
  247.     else {
  248.         if (! PtrValid((**thread).stack)) return(false);
  249.         if (! (**thread).entry) return(false);
  250.     }
  251.     return(true);
  252. }
  253.  
  254. /*----------------------------------------------------------------------------*/
  255. /* thread queue */
  256. /*----------------------------------------------------------------------------*/
  257.  
  258. /* ThreadCount returns the number of threads in the queue. */
  259. short ThreadCount(void)
  260. {
  261.     return(gThread.count);
  262. }
  263.  
  264. /* ThreadMain returns the main thread. */
  265. ThreadHandle ThreadMain(void)
  266. {
  267.     return(gThread.main);
  268. }
  269.  
  270. /* ThreadActive returns the currently active thread. */
  271. ThreadHandle ThreadActive(void)
  272. {
  273.     return(gThread.active);
  274. }
  275.  
  276. /* ThreadFirst returns the first thread in the queue of threads. */
  277. ThreadHandle ThreadFirst(void)
  278. {
  279.     return(gThread.queue);
  280. }
  281.  
  282. /* ThreadNext returns the next thread in the circular queue of threads. */
  283. ThreadHandle ThreadNext(ThreadHandle thread)
  284. {
  285.     return(LLHNext(thread) ? LLHNext(thread) : gThread.queue);
  286. }
  287.  
  288. /*----------------------------------------------------------------------------*/
  289. /* information about a thread */
  290. /*----------------------------------------------------------------------------*/
  291.  
  292. /* ThreadStackSpace returns the amount of stack space remaining in the
  293.     specified thread. There are at least the returned number of bytes
  294.     between the thread's stack pointer and the bottom of the thread's
  295.     stack, though slightly more space may be available to the application
  296.     due to overhead from the thread library. */
  297. size_t ThreadStackSpace(ThreadHandle thread)
  298. {
  299.     size_t result;
  300.     Ptr stack_bottom;
  301.     jmp_buf registers;
  302.     
  303.     require(ThreadValid(thread));
  304.     if (thread == gThread.main)
  305.         stack_bottom = GetApplLimit();
  306.     else
  307.         stack_bottom = (**thread).stack;
  308.     if (thread == gThread.active) {
  309.         (void) setjmp(registers);
  310.         check((Ptr) registers[a7] >= stack_bottom);
  311.         result = (Ptr) registers[a7] - stack_bottom;
  312.     }
  313.     else {
  314.         check((Ptr) (**thread).env[a7] >= stack_bottom);
  315.         result = (Ptr) (**thread).env[a7] - stack_bottom;
  316.     }
  317.     ensure(result >= 0);
  318.     return(result);
  319. }
  320.  
  321. /*----------------------------------------------------------------------------*/
  322. /* segmentation support */
  323. /*----------------------------------------------------------------------------*/
  324.  
  325. /* ThreadStackFrame returns information about the specified thread's
  326.     stack and stack frame. This information is needed for executing
  327.     a stack trace during automatic segment unloading in SegmentLib.c.
  328.     You should never need to call this function. This function will
  329.     work correctly even if no threads exist. */
  330. void ThreadStackFrame(ThreadHandle thread, ThreadStackFrameType *frame)
  331. {
  332.     jmp_buf registers;
  333.     
  334.     require(! thread || ThreadValid(thread));
  335.     memclr(frame, sizeof(ThreadStackFrameType));
  336.     if (thread == gThread.main) {
  337.         /* The main thread always uses the application's stack, so we
  338.             just use the current values of the stack base and registers.
  339.             This is the default case if there are no threads (in which
  340.             case 'thread' and gThread.main are both null). */
  341.         (void) setjmp(registers);
  342.         frame->stack_top = GetCurStackBase();
  343.         frame->stack_bottom = (Ptr) registers[a7];
  344.         frame->register_a6 = *(Ptr *) registers[a6];
  345.     }
  346.     else if (thread == gThread.active) {
  347.         /* The active thread uses a stack allocated from a pointer in the
  348.             heap, but registers a6 and a7 point into this stack so we
  349.             can just use their current values. */
  350.         check(thread != NULL);
  351.         (void) setjmp(registers);
  352.         frame->stack_top = (**thread).stack + PtrSize((**thread).stack);
  353.         frame->stack_bottom = (Ptr) registers[a7];
  354.         frame->register_a6 = *(Ptr *) registers[a6];
  355.     }
  356.     else {
  357.         /* All other threads are inactive and use their own private stack,
  358.             so we use the values of registers a6 and a7 that were saved
  359.             when the thread was suspended. */
  360.         check(thread != NULL);
  361.         frame->stack_top = (**thread).stack + PtrSize((**thread).stack);
  362.         frame->stack_bottom = (Ptr) (**thread).env[a7];
  363.         frame->register_a6 = (Ptr) (**thread).env[a6];
  364.     }
  365.     ensure(! frame->register_a6 ||
  366.             (frame->stack_bottom <= frame->register_a6 &&
  367.               frame->register_a6 <= frame->stack_top));
  368.     ensure((Ptr) 0 < frame->stack_bottom && frame->stack_bottom <= frame->stack_top);
  369. }
  370.  
  371. /*----------------------------------------------------------------------------*/
  372. /* thread scheduling and context switching */
  373. /*----------------------------------------------------------------------------*/
  374.  
  375. /* ThreadSchedule returns the next thread to activate. Threads are maintained
  376.     in a queue and are scheduled in a round-robbin fashion. Starting with the
  377.     current thread, the queue of threads is searched for the next thread whose
  378.     wake time has arrived. The first such thread found is returned. 
  379.         
  380.     In addition to the round-robbin scheduling shared with all threads, the
  381.     main thread will also be activated if any events are pending in the event
  382.     queue. The application can then immediately handle the events, allowing
  383.     the application to remain responsive to user actions such as mouse clicks.
  384.     The main thread will also be activated if no other threads are scheduled
  385.     for activation, which allows the application either to continue with
  386.     its main processing or to call WaitNextEvent and sleep until a thread
  387.     needs to be activated or some other task or event needs to be handled.
  388.     Since the thread scheduler calls EventAvail, background applications
  389.     will continue to receive processing time, even if the main thread is
  390.     never activated while some compute intensive thread is executing. But,
  391.     since EventAvail can be a slow trap (especially when it yields the
  392.     processor to another application), it is only executed every few ticks.
  393.     Note: if I figure out a faster way to test for events then the call
  394.     to EventAvail will be removed, and background applications won't
  395.     get time when ThreadSchedule is called.  */
  396. ThreadHandle ThreadSchedule(void)
  397. {
  398.     #define EVENT_INTERVAL (6)    /* number of ticks between calls to EventAvail */
  399.     static TicksType lastEvent;/* last time we called EventAvail */
  400.     ThreadHandle newthread;        /* thread to switch to */
  401.     EventRecord event;            /* for checking for events */
  402.     TicksType ticks;                /* current tick count */
  403.     
  404.     require(ThreadValid(gThread.active));
  405.     if (gThread.active != gThread.main &&
  406.          TickCount() - lastEvent >= EVENT_INTERVAL &&
  407.          EventAvail(everyEvent, &event))
  408.     {
  409.         /* an event is pending, so activate main thread */
  410.         lastEvent = TickCount();
  411.         newthread = gThread.main;
  412.     }
  413.     else {
  414.         /* search for a thread to activate in round-robbin fashion */
  415.         ticks = TickCount();
  416.         newthread = ThreadNext(gThread.active);
  417.         while (newthread != gThread.active && (**newthread).wake > ticks)
  418.             newthread = ThreadNext(newthread);
  419.         if (newthread == gThread.active && (**newthread).wake > ticks) {
  420.             /* no thread needs to be woken up, so activate main thread */
  421.             newthread = gThread.main;
  422.         }
  423.     }
  424.     ensure(ThreadValid(newthread));
  425.     return(newthread);
  426. }
  427.  
  428. /* ThreadSave saves the context of the active thread. It is called before
  429.     a thread is suspended, but after setjmp has saved the CPU's context for
  430.     the thread. */
  431. static void ThreadSave(void)
  432. {
  433.     require(ThreadValid(gThread.active));
  434.     
  435.     /* save exception state (ExceptionCopy won't move memory) */
  436.     ExceptionCopy(&gException, &(**gThread.active).exception);
  437.     
  438.     #if THREAD_SAVE_GLOBALS
  439.         /* save low-memory globals */
  440.         (**gThread.active).heapEnd = *(Ptr *) HeapEnd;
  441.         (**gThread.active).applLimit = *(Ptr *) ApplLimit;
  442.         (**gThread.active).hiHeapMark = *(Ptr *) HiHeapMark;
  443.     #endif /* THREAD_SAVE_GLOBALS */
  444.  
  445.     /* call the application's suspend function */
  446.     if ((**gThread.active).suspend)
  447.         (**gThread.active).suspend((**gThread.active).data);
  448. }
  449.  
  450. /* ThreadRestore restores the context of the active thread. It is called
  451.     before a thread resumes execution, but after the thread's stack has
  452.     been restored. */
  453. static void ThreadRestore(void)
  454. {
  455.     require(ThreadValid(gThread.active));
  456.     
  457.     /* Restore the exception state for the thread. The first
  458.         time this is executed it clears and resets the exception
  459.         state, since the exception field of the thread structure
  460.         is initially filled with nulls. The exception state must
  461.         be cleared before ThreadStart is called, since ThreadStart
  462.         has its own exception handlers. (ExceptionCopy won't move
  463.         memory.) */
  464.     ExceptionCopy(&(**gThread.active).exception, &gException);
  465.  
  466.     #if THREAD_SAVE_GLOBALS
  467.         /* Set the low-memory globals for the thread. We bypass any
  468.             traps or glue (like SetApplLimit) to keep the OS from
  469.             preventing us from changing these globals. */
  470.         *(Ptr *) HeapEnd = (**gThread.active).heapEnd;
  471.         *(Ptr *) ApplLimit = (**gThread.active).applLimit;
  472.         *(Ptr *) HiHeapMark = (**gThread.active).hiHeapMark;
  473.     #endif /* THREAD_SAVE_GLOBALS */
  474.  
  475.     /* dispose of the memory allocated for the previous thread (see ThreadEnd) */
  476.     if (gThread.dispose) {
  477.         PtrEnd((**gThread.dispose).stack);
  478.         HandleEnd(gThread.dispose);
  479.         gThread.dispose = NULL;
  480.     }
  481.     
  482.     /* call the application's resume function */
  483.     if ((**gThread.active).resume)
  484.         (**gThread.active).resume((**gThread.active).data);
  485. }
  486.  
  487. /*    ThreadActivate activates the specified thread. The context switch is
  488.     accomplished by saving the current stack, restoring the new thread's stack,
  489.     and then calling longjmp, which jumps to the environment saved with setjmp
  490.     when the thread being activated was last suspended. We don't have to do any
  491.     assembly language glue since setjmp saved the value of the stack pointer,
  492.     which at the time of the call to setjmp pointed somewhere in the thread's
  493.     stack. The longjmp instruction will restore the value of the stack pointer
  494.     and will jump to the statement from which to resume the thread. Longjmp
  495.     also handles all register saving and restoring, freeing us from the
  496.     maintenance problems of supporting different register sets (e.g., floating
  497.     point registers). */
  498. void ThreadActivate(ThreadHandle thread)
  499. {
  500.     ThreadHandle active;
  501.     
  502.     require(ThreadValid(gThread.active));
  503.     require(ThreadValid(thread));
  504.     active = gThread.active;
  505.     if (thread != active) {
  506.         if (setjmp((**active).env) == THREAD_SAVE) {
  507.             /* the thread is being deactivated, so save the active thread's
  508.                 context */
  509.             ThreadSave();
  510.             
  511.             /* Jump to the specified thread. This suspends the current thread
  512.                 and returns at the setjmp statement above (unless this is the
  513.                 first time the thread is being executed, in which case it
  514.                 returns at the setjmp statement in ThreadBegin). The contents
  515.                 of the stack will be correct as soon as the longjmp has
  516.                 completed, but ThreadRestore must be called before the
  517.                 thread can resume. */
  518.             gThread.active = thread;
  519.             longjmp((**thread).env, THREAD_RUN);
  520.             check(false); /* doesn't return */
  521.         }
  522.         else {
  523.             /* the thread is being activated, so restore the thread's context */
  524.             ThreadRestore();
  525.         }
  526.     }
  527. }
  528.  
  529. /* ThreadYield activates the next scheduled thread as determined by
  530.     ThreadSchedule. The 'sleep' parameter specifies the maximum amount
  531.     of time that the current thread can remain inactive. */
  532. void ThreadYield(TicksType sleep)
  533. {
  534.     TicksType ticks;
  535.  
  536.     /* Set active thread's wakeup time before we run the scheduler and before
  537.         the active thread is suspended. (We're careful with overflow since the
  538.         sleep parameter could be ULONG_MAX.) */
  539.     require(ThreadValid(gThread.active));
  540.     require(sleep >= 0);
  541.     ticks = TickCount();
  542.     if (sleep > ULONG_MAX - ticks)
  543.         (**gThread.active).wake = ULONG_MAX;
  544.     else
  545.         (**gThread.active).wake = ticks + sleep;
  546.     ThreadActivate(ThreadSchedule());
  547. }
  548.  
  549. /* Return the maximum time till the next call to ThreadYield. The interval
  550.     is computed by subtracting the current time from each thread's wake time,
  551.     giving the amount of time that each thread can remain inactive. The
  552.     minimum of these times gives the maximum amount of time till the next
  553.     call to ThreadYield. The wake time of the current thread is ignored,
  554.     since the thread is already active. You can use the returned value
  555.     to determine the maximum sleep value to pass to WaitNextEvent. */
  556. TicksType ThreadYieldInterval(void)
  557. {
  558.     ThreadHandle thread;    /* for iterating through list of threads */
  559.     TicksType interval;    /* interval till next call to ThreadYield */
  560.     TicksType elapsed;    /* time elapsed since a thread was last executed */
  561.     TicksType ticks;        /* current tick count */
  562.     
  563.     ticks = TickCount();
  564.     interval = LONG_MAX;
  565.     for (thread = gThread.queue; thread && interval; thread = LLHNext(thread)) {
  566.         if (thread != gThread.active) {
  567.             if ((**thread).wake <= ticks)
  568.                 interval = 0;
  569.             else if ((**thread).wake - ticks < interval)
  570.                 interval = (**thread).wake - ticks;
  571.         }
  572.     }
  573.     ensure(interval >= 0);
  574.     return(interval);
  575. }
  576.  
  577. /*----------------------------------------------------------------------------*/
  578. /* thread creation and destruction */
  579. /*----------------------------------------------------------------------------*/
  580.  
  581. /* ThreadEnd removes the thread from the queue and disposes of the memory
  582.     allocated for the thread. If the thread is the active thread then the
  583.     next thread in the queue is activated. All threads (other than the main
  584.     thread) must be disposed of before the main thread can be disposed of. */
  585. void ThreadEnd(ThreadHandle thread)
  586. {
  587.     ThreadHandle newthread = NULL;/* thread to activate if disposing of the active thread */
  588.  
  589.     if (thread) {
  590.     
  591.         if (thread == gThread.main) {
  592.             /* disposing of main thread, so clear globals */
  593.             check(gThread.active == thread);
  594.             check(gThread.count == 1);
  595.             gThread.main = gThread.active = NULL;
  596.         }
  597.         else if (thread == gThread.active) {
  598.             /* disposing of the active thread, so activate the next scheduled
  599.                 thread, or the main thread if the next scheduled thread is the
  600.                 active thread */
  601.             newthread = ThreadSchedule();
  602.             if (newthread == gThread.active)
  603.                 newthread = gThread.main;
  604.         }
  605.         check(! newthread || newthread != gThread.active);
  606.         
  607.         /* remove thread from queue */
  608.         gThread.queue = LLHDelete(gThread.queue, thread);
  609.         check(gThread.count > 0);
  610.         gThread.count--;
  611.         check(gThread.count > 0 ? gThread.queue != NULL : ! gThread.queue);
  612.         
  613.         /* activate the next thread (we don't call ThreadActivate since
  614.             there's no need to save the now-defunct thread's state) */
  615.         if (thread == gThread.active && newthread) {
  616.         
  617.             /* We're disposing of the active thread, but we can't dispose of
  618.                 the thread's stack since we're still using that stack. So
  619.                 we delay disposal of the thread until the next thread is
  620.                 activated; the thread will be disposed of in ThreadRestore,
  621.                 which is executed when the next thread is resumed. */
  622.             check(! gThread.dispose);
  623.             gThread.dispose = thread;
  624.             gThread.active = newthread;
  625.             longjmp((**newthread).env, THREAD_RUN);
  626.             check(false); /* doesn't return */
  627.         }
  628.         else {
  629.             /* dispose of the memory allocated for the thread */
  630.             PtrEnd((**thread).stack);
  631.             HandleEnd(thread);
  632.         }
  633.     }
  634. }
  635.  
  636. /* ThreadBeginMain creates the main application thread. You must call this
  637.     function before creating any other threads with ThreadBegin. The
  638.     'resume', 'suspend', and 'data' parameters have the same meanings
  639.     as the parameters to ThreadBegin.
  640.     
  641.     There are several important differences between the main thread and
  642.     all subsequently created threads.
  643.     
  644.     - The main thread is responsible for handling events sent to the
  645.     application, and is therefore scheduled differently than other threads;
  646.     see ThreadSchedule for details.
  647.  
  648.     - While other threads don't begin executing until they're scheduled to
  649.     execute, the main thread is made the active thread and starts to run as
  650.     soon as ThreadBeginMain returns.
  651.     
  652.     - Since other threads have a special entry point, they are automatically
  653.     disposed of when that entry point returns. The main thread, lacking
  654.     any special entry point, must be disposed of by the application. You
  655.     should call ThreadEnd, passing it the thread returned by ThreadBeginMain
  656.     before exiting your application.
  657.  
  658.     - The main thread uses the application's stack and context; no private
  659.     stack is allocated for the main thread. Initially, there is therefore
  660.     no need to change any values to start executing the thread, and
  661.     no special entry point is required. But, like all other threads, the main
  662.     thread's context will be saved whenever it is suspended to allow another
  663.     thread to execute, and its context will be restored when it is resumed.
  664. */
  665. ThreadHandle ThreadBeginMain(ThreadProcType suspend, ThreadProcType resume,
  666.     void *data)
  667. {
  668.     volatile ThreadHandle thread = NULL; /* the new thread */
  669.  
  670.     require(! gThread.main);
  671.     TRY {
  672.     
  673.         /* allocate thread structure */
  674.         thread = HandleBeginClear(sizeof(ThreadType));
  675.         
  676.         /* initialize thread structure */
  677.         (**thread).suspend = suspend;
  678.         (**thread).resume = resume;
  679.         (**thread).data = data;
  680.  
  681.         /* save values of low-memory globals */
  682.         #if THREAD_SAVE_GLOBALS
  683.             (**thread).heapEnd = *(Ptr *) HeapEnd;
  684.             (**thread).applLimit = *(Ptr *) ApplLimit;
  685.             (**thread).hiHeapMark = *(Ptr *) HiHeapMark;
  686.         #endif /* THREAD_SAVE_GLOBALS */
  687.         
  688.         /* now that the thread is ready to use, append it to the queue of threads
  689.             so that it can be scheduled for execution */
  690.         gThread.queue = LLHAppend(gThread.queue, thread);
  691.         gThread.count++;
  692.         
  693.         /* make this thread the active and main thread */
  694.         gThread.active = thread;
  695.         gThread.main = thread;
  696.     } CATCH {
  697.         ThreadEnd(thread);
  698.     } ENDTRY;
  699.     ensure(ThreadValid(thread));
  700.     ensure(thread == gThread.main);
  701.     ensure(gThread.active == gThread.main);
  702.     ensure(gThread.queue == gThread.main);
  703.     return(thread);
  704. }
  705.  
  706. /* ThreadStart is called when a thread is first executed; it calls the
  707.     thread's entry point and takes care of intercepting exceptions. The
  708.     exception handlers that were set up before the thread was created are on
  709.     a different stack which is not valid (it's swapped out) when the CLEANUP
  710.     handler in ThreadStart gets executed, so we must prevent the exception
  711.     from propagating. When the thread returns it is disposed of and the
  712.     next scheduled thread is activated. */
  713. static void ThreadStart(void)
  714. {
  715.     require(ThreadValid(gThread.active));
  716.     TRY {
  717.     
  718.         /* call the thread's entry point */
  719.         (**gThread.active).entry((**gThread.active).data);
  720.         
  721.     } CLEANUP {
  722.     
  723.         /* prevent exceptions from propagating */
  724.         NOPROPAGATE;
  725.         
  726.         /* dispose of the thread and switch to the next scheduled thread */
  727.         ThreadEnd(gThread.active);
  728.         
  729.     } ENDTRY;
  730.     ensure(false); /* never returns */
  731. }
  732.  
  733. /* ThreadBegin creates a new thread. You must create the main thread with
  734.     ThreadBeginMain before you can call ThreadBegin. The 'entry' parameter is
  735.     a pointer to a function that is called to start executing the thread. The
  736.     'suspend' parameter is a pointer to a function called whenever the
  737.     thread is suspended. You can use the 'suspend' function to save
  738.     additional application defined context for the thread. The 'resume'
  739.     parameter is a pointer to a function call whenever the thread is
  740.     resumed. You can use the 'resume' function to save additional application
  741.     defined context for the thread. The 'data' parameter is passed to the
  742.     'entry', 'suspend', and 'resume' functions and may contain any application
  743.     defined data. The 'stack_size' parameter specifies the size of the stack
  744.     needed by the thread. The requested stack size should be large enough to
  745.     contain all function calls and local variables and parameters. If
  746.     'stack_size' is zero then the default stack size THREAD_STACK_SIZE
  747.     is used. If your thread crashes (often with a corrupted heap) try
  748.     increasing the thread's stack size.
  749.     
  750.     The new thread is appended to the end of the thread queue, making it
  751.     eligible for scheduling whenever ThreadYield is called. ThreadBegin
  752.     returns immediately after creating the new thread. The thread, however,
  753.     is not executed immediately, but rather is executed whenever it is
  754.     scheduled to execute. At that time, the function specified in the 'entry'
  755.     parameter is called. When the function has returned, the thread is removed
  756.     from the queue of threads and its stack and any private storage allocated
  757.     by ThreadBegin are disposed of. */
  758. ThreadHandle ThreadBegin(ThreadProcType entry,
  759.     ThreadProcType suspend, ThreadProcType resume,
  760.     void *data, size_t stack_size)
  761. {
  762.     volatile ThreadHandle thread = NULL; /* the new thread */
  763.     Ptr stack_top = NULL;            /* pointer to top of thread's stack */
  764.     Ptr stack = NULL;                    /* or pointer to thread's stack */
  765.  
  766.     require(ThreadValid(gThread.main));
  767.     require(entry != NULL);
  768.     require(0 <= stack_size);
  769.  
  770.     TRY {
  771.     
  772.         /* allocate thread structure */
  773.         thread = HandleBeginClear(sizeof(ThreadType));
  774.         
  775.         /* initialize thread structure */
  776.         (**thread).entry = entry;
  777.         (**thread).suspend = suspend;
  778.         (**thread).resume = resume;
  779.         (**thread).data = data;
  780.         
  781.         /* The main thread uses the application's regular stack, while
  782.             nonrelocatable blocks are allocated to contain the stacks of
  783.             all other threads. Since all stacks persist until the thread
  784.             that created them terminates, you can create any object you
  785.             require on the stacks, including window records and parameter
  786.             blocks. The main advantage of using separate stacks, however,
  787.             is the speed of context switches. A context switch involves
  788.             only a call to longjmp, and no time consuming saving and
  789.             restoring of stacks is necessary. */
  790.         if (! stack_size)
  791.             stack_size = THREAD_STACK_SIZE;
  792.         stack = PtrBegin(stack_size);
  793.         stack_top = stack + stack_size;
  794.         (**thread).stack = stack;
  795.         
  796.         /* Since all threads other than the main thread use stacks
  797.             allocated in the application's heap, we need to disable the
  798.             stack sniffer VBL task by setting the low-memory global
  799.             variable StkLowPt to 0. Otherwise, the stack sniffer would
  800.             generate system error #28. Thanks to Anton Rang
  801.             (rang@icicle.winternet.mpls.mn.us) for how to disable
  802.             the stack sniffer. */
  803.         SetStkLowPt(NULL);
  804.         
  805.         #if THREAD_SAVE_GLOBALS
  806.             /* Certain low-memory globals divide the stack and heap. We change
  807.                 the globals when a thread other than the main thread is activated
  808.                 so that certain OS traps will work correctly. */
  809.             (**thread).heapEnd = stack_top;
  810.             (**thread).applLimit = stack_top;
  811.             (**thread).hiHeapMark = stack_top;
  812.         #endif /* THREAD_SAVE_GLOBALS */
  813.         
  814.         /* Setup the new thread's jump environment so that we'll jump
  815.             here when the thread is first activated. */
  816.         if (setjmp((**thread).env) == THREAD_RUN) {
  817.  
  818.             /* We're now executing the *new* thread (I know, it doesn't
  819.                 look like it, but it's all due to the magic [hell?] of
  820.                 non-local gotos and global variables). At this point,
  821.                 the stack is empty, so we can't access any local variables.
  822.                 All subsequent executions of the thread will go through the
  823.                 setjmp call in ThreadActivate. */
  824.  
  825.             /* restore the context of the thread */
  826.             ThreadRestore();
  827.             
  828.             /* run the thread */
  829.             ThreadStart();
  830.             check(false); /* never returns */
  831.         }
  832.  
  833.         /* Munge the registers in the jump environment so that we start from
  834.             the top of the thread's stack, and so that tracing function calls
  835.             on the stack will stop when it reaches the first function executed
  836.             in the new thread (which will always be ThreadBegin, ensuring that
  837.             the thread library segment is not unloaded by my automatic segment
  838.             unloading routines). */
  839.         (**thread).env[a7] = (long) stack_top;
  840.         (**thread).env[a6] = 0;
  841.  
  842.         /* now that the thread is ready to use, append it to the queue of threads
  843.             so that it can be scheduled for execution */
  844.         gThread.queue = LLHAppend(gThread.queue, thread);
  845.         gThread.count++;
  846.         
  847.         /* We've now successfully created a new thread and set things up so
  848.             that the first time the thread is invoked we'll call ThreadStart.
  849.             We let the application call ThreadYield in its own time to switch
  850.             contexts. In other words, the new thread doesn't start executing
  851.             until it has been scheduled to start. */
  852.  
  853.     } CATCH {
  854.         ThreadEnd(thread);
  855.     } ENDTRY;
  856.     ensure(ThreadValid(thread));
  857.     return(thread);
  858. }
  859.